package org.su.mamamiya.web.processor;

import com.squareup.javapoet.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.su.mamamiya.core.ast.AstProccessor;
import org.su.mamamiya.core.ast.DocHelper;
import org.su.mamamiya.core.ast.Param;
import org.su.mamamiya.core.ast.ParamDoc;
import org.su.mamamiya.web.annotation.Rpc;
import org.su.mamamiya.web.annotation.RpcSkip;
import org.su.mamamiya.web.utils.ProcessorHelper;


import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static javax.lang.model.SourceVersion.latestSupported;


/**
 * Created by yangchuan on 16/12/8.
 */
//@AutoService(Processor.class)
@SupportedAnnotationTypes({"org.su.mamamiya.web.annotation.Rpc"})
public class ContextProcessor extends AbstractProcessor {
    private static final String SUFFIX = "Rpc";

    private Types typeUtils;
    private Elements elementUtils;
    private Filer filer;
    private Messager messager;

    private AstProccessor astProccessor;
    private DocHelper docHelper;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        typeUtils = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        astProccessor = new AstProccessor(processingEnv);
        docHelper = new DocHelper(astProccessor);
    }

    /**
     * {@inheritDoc}
     *
     * @param annotations
     * @param roundEnv
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return processRpc(annotations, roundEnv);
    }

    public boolean processRpc(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();

        String className = null;
        MethodSpec.Builder method = null;

        String urls = null;
        for (Element e : roundEnv.getElementsAnnotatedWith(Rpc.class)) {
            if (e.getKind() != ElementKind.INTERFACE) {
                messager.printMessage(Diagnostic.Kind.ERROR, String.format("Only interface can be annotated with @%s", Rpc.class.getSimpleName()));
                return true;
            }
            className = e.toString();
            TypeElement superClassName = elementUtils.getTypeElement(className);
            AnnotationSpec.Builder feignClientBuilder = AnnotationSpec.builder(FeignClient.class);
            Rpc rpc = e.getAnnotation(Rpc.class);
            /**
             * value或name 用于微服务中的服务名，等同于已经废弃额serviceId
             */
            feignClientBuilder.addMember("value", "$S", rpc.value());

            /**
             * 当同一个微服务的多个类提供了服务，就会导致我们的feignClient的value一样，这样客户端相当于使用了相同的bean，
             * 客户端只能设置spring.main.allow-bean-definition-overriding = true 来解决
             * 另外一种方式就是设置feignClient的contextId属性
             * contextId属性相当于一种别名
             *
             */
            feignClientBuilder.addMember("contextId", "$S", rpc.value()+"-"+e.getSimpleName());

            urls = "/" + e.getSimpleName();
            TypeSpec.Builder classBuilder = TypeSpec.interfaceBuilder(e.getSimpleName() + SUFFIX)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(feignClientBuilder.build())
                    .addSuperinterface(TypeName.get(superClassName.asType()));


            String[] contexts = ProcessorHelper.getUrlMapping(rpc, rpc.context());

            for (Element enclosedElement : e.getEnclosedElements()) {
                if (enclosedElement.getKind() == ElementKind.METHOD) {
                    if(enclosedElement.getAnnotation(RpcSkip.class) != null){
                        continue;
                    }

                    ExecutableElement amethod = (ExecutableElement) enclosedElement;
                    if (!amethod.getModifiers().contains(Modifier.PUBLIC)) {
                        continue;
                    }
                    String methodName = enclosedElement.getSimpleName().toString();
                    messager.printMessage(Diagnostic.Kind.NOTE, "method.name:" + methodName);


                    List<? extends AnnotationMirror> ams = enclosedElement.getAnnotationMirrors();

                    method = MethodSpec.methodBuilder(methodName)
                            .addModifiers(Modifier.ABSTRACT)
                            // .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC).returns(TypeName.get(amethod.getReturnType()));

                    //if (contexts == null || contexts.length == 0) {
                        /*for (AnnotationMirror am : ams) {
                            messager.printMessage(Diagnostic.Kind.NOTE, "am:" + am.toString());
                            method.addAnnotation(AnnotationSpec.get(am));
                        }*/
                    // } else {
                    List<Param> params = new ArrayList<>();
                    Param param = null;
                    boolean postFlag = false;
                    for (VariableElement ve : amethod.getParameters()) {
                        String arg = ve.getSimpleName().toString();
                        param = ProcessorHelper.getParam(ve,arg);
                        params.add(param);
                        if(!postFlag) {
                            postFlag = "form".equals(param.getParamType()) || "body".equals(param.getParamType());
                        }
                    }

                    if (ProcessorHelper.setMapping(method, enclosedElement.getAnnotation(RequestMapping.class), contexts[0]) ||
                            ProcessorHelper.setMapping(method, enclosedElement.getAnnotation(PostMapping.class), contexts[0]) ||
                            ProcessorHelper.setMapping(method, enclosedElement.getAnnotation(GetMapping.class), contexts[0]) ||
                            ProcessorHelper.setMapping(method, enclosedElement.getAnnotation(DeleteMapping.class), contexts[0]) ||
                            ProcessorHelper.setMapping(method, enclosedElement.getAnnotation(PutMapping.class), contexts[0])) {
                    } else {
                        boolean flag = methodName.startsWith("get") || methodName.startsWith("query") || methodName.startsWith("load");
                        if (flag && !postFlag) {
                            ProcessorHelper.setDefaultMapping(amethod, method,
                                    new RequestMethod[]{RequestMethod.GET}, contexts[0], urls + "/" + methodName);
                        }else{
                            ProcessorHelper.setDefaultMapping(amethod, method,
                                    new RequestMethod[]{RequestMethod.POST}, contexts[0], urls + "/" + methodName);
                        }
                    }
                    //}

                    String txt = astProccessor.getDoc(amethod);
                    List<ParamDoc> docs = docHelper.getParamDoces(txt);
                    //int i = 0;
                    String arg = null;
                    for (VariableElement ve : amethod.getParameters()) {
                        arg = ve.getSimpleName().toString();//"arg" + ++i;
                        ParameterSpec.Builder psb = ParameterSpec.builder(TypeName.get(ve.asType()), arg);
                        ams = ve.getAnnotationMirrors();
                        for (AnnotationMirror am : ams) {
                            messager.printMessage(Diagnostic.Kind.WARNING, "params.am:" + am.toString());

                            psb.addAnnotation(AnnotationSpec.get(am));
                        }
                        Param paramItem = new Param();
                        for(ParamDoc pd : docs){
                            if(pd.getName().equals(arg)){
                                paramItem.setRequired(pd.isNotNull());
                            }
                        }
                        ProcessorHelper.setDefaultPramater(ve, psb, arg,paramItem);

                        method.addParameter(psb.build());
                    }
                    classBuilder = classBuilder.addMethod(method.build());
                }
            }


            PackageElement pkg = elementUtils.getPackageOf(superClassName);
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
            if (packageName == null) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Rpc find package name is null.");
                return true;
            }

            TypeSpec typeSpec = classBuilder.build();
            // Write file
            try {
                JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
            } catch (IOException e1) {
                messager.printMessage(Diagnostic.Kind.ERROR, e1.getMessage());
                return true;
            }
        }

        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return latestSupported();
    }


}
